#include "PhysxLogic.h"
#include <iostream>

#include <NxPlaneShape.h>
#include <NxPlaneShapeDesc.h>
#include <NxBoxShapeDesc.h>
#include <NxSphereShapeDesc.h>

//variables
NxPhysicsSDK *PhysxLogic::physicsSDK = NULL;
NxScene *PhysxLogic::physicsScene = NULL;

NxVec3 *PhysxLogic::camPos = NULL; 
NxVec3 *PhysxLogic::camDir = NULL; 

bool PhysxLogic::Init()
{
    NxPhysicsSDKDesc sdkDesc;
    physicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, NULL, NULL, sdkDesc);
    if(physicsSDK == NULL) 
    {
        return false;
    }

    NxSceneDesc sceneDesc;
    sceneDesc.gravity = NxVec3(0.0f, -9.81f, 0.0f);
    physicsScene = physicsSDK->createScene(sceneDesc);
    if(physicsScene == NULL) 
    {
        return false;
    }
    return true;
}

void PhysxLogic::PrepareScene()
{
    camPos = new NxVec3(15.0f, 10.0f, 15.0f);
    camDir = new NxVec3(-0.7f, -0.3f, -0.7f);

    //default material
    NxMaterial *defaultMaterial = physicsScene->getMaterialFromIndex(0);
    defaultMaterial->setRestitution(0.01f);
    defaultMaterial->setStaticFriction(1.0f);
    defaultMaterial->setDynamicFriction(2.0f);


    //create plane
    NxPlaneShapeDesc planeDesc;
    NxActorDesc groundActorDesc;
    groundActorDesc.shapes.pushBack(&planeDesc);
    physicsScene->createActor(groundActorDesc);


    //create boxes
    NxActor *box1big = CreateBox(NxVec3(0.0f, 4.0f, 0.0f), NxVec3(2.0f, 4.0f, 2.0f), 3.0f);
    NxActor *box1small = CreateBox(NxVec3(0.0f, 9.0f, 0.0f), NxVec3(0.5f, 1.0f, 0.5f), 5.0f);

    NxActor *box2big = CreateBox(NxVec3(2.0f, 4.0f, -5.0f), NxVec3(2.0f, 4.0f, 2.0f), 3.0f);
    NxActor *box2small = CreateBox(NxVec3(2.0f, 9.0f, -5.0f), NxVec3(0.5f, 1.0f, 0.5f), 5.0f);

    NxActor *box3big = CreateBox(NxVec3(-5.0f, 4.0f, 2.0f), NxVec3(2.0f, 4.0f, 2.0f), 3.0f);
    NxActor *box3small = CreateBox(NxVec3(-5.0f, 9.0f, 2.0f), NxVec3(0.5f, 1.0f, 0.5f), 5.0f);
    //
}

void PhysxLogic::SimulateAndRender()
{
    if(physicsScene == NULL) {
        return;
    }

    physicsScene->simulate(1.0f/60.0f);
    physicsScene->flushStream();

    Draw();

    physicsScene->fetchResults(NX_RIGID_BODY_FINISHED, true);
}

void PhysxLogic::Exit()
{
    if(physicsSDK != NULL)
    {
        if(physicsScene != NULL)
            physicsSDK->releaseScene(*physicsScene);
        NxReleasePhysicsSDK(physicsSDK);
        physicsScene = NULL;
        physicsSDK = NULL;
    }
    delete camPos;
    delete camDir;
}

void PhysxLogic::ThrowBall(float size, float power)
{
    NxVec3 &p = *camPos;
    NxVec3 vel = *camDir;

    //random direction
    int maxAngle = 45; //degree
    int r = NxMath::rand(0, maxAngle);
    NxReal rotationAngle = r - maxAngle/2.0f;
    NxQuat q;
    q.fromAngleAxis(rotationAngle, NxVec3(0.0f, 1.0f, 0.0f));
    q.rotate(vel);
    //

    vel.normalize();
    vel*=power;	

    NxBodyDesc bodyDesc;
    bodyDesc.angularDamping	= 0.5f;
    bodyDesc.linearDamping	= 0.01f;
    bodyDesc.linearVelocity = vel;

    NxSphereShapeDesc sphereDesc;
    sphereDesc.radius = size;

    NxActorDesc actorDesc;
    actorDesc.shapes.pushBack(&sphereDesc);
    actorDesc.body			= &bodyDesc;
    actorDesc.density		= 10.0f;
    actorDesc.globalPose.t  = p;

    physicsScene->createActor(actorDesc);
}


void PhysxLogic::HandleKeyboardEvent(unsigned char key, int x, int y)
{
    switch(key)
    {
        case ' ':
        {
            double size = NxMath::rand(0.2f, 1.8f);
            ThrowBall(size, 80.0f);
            break;
        }
        case 'q': //reset scene
        {
            unsigned int nbActors = physicsScene->getNbActors();
            NxActor** actors = physicsScene->getActors();
            while(nbActors--) {
                NxActor* actor = *actors++;
                physicsScene->releaseActor(*actor);
            }
            delete camPos;
            delete camDir;
            camPos = NULL;
            camDir = NULL;

            PrepareScene();
            break;
        }
    }
}

NxActor *PhysxLogic::CreateBox(const NxVec3 &pos, const NxVec3 &dim, float density)
{
    if(physicsScene == NULL)
        return NULL;

    NxBodyDesc boxBodyDesc;

    NxBoxShapeDesc boxShapeDesc;
    boxShapeDesc.dimensions = dim;

    NxActorDesc boxActorDesc;
    boxActorDesc.shapes.pushBack(&boxShapeDesc);
    boxActorDesc.body = &boxBodyDesc;
    boxActorDesc.density = density;
    boxActorDesc.globalPose.t = pos;	
    return physicsScene->createActor(boxActorDesc);
}

void PhysxLogic::Draw()
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(80.0, (GLdouble)glutGet(GLUT_WINDOW_WIDTH)/(float)glutGet(GLUT_WINDOW_HEIGHT), 1.0, 10000.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(camPos->x, camPos->y, camPos->z, camPos->x + camDir->x, camPos->y + camDir->y, camPos->z + camDir->z, 0.0, 1.0, 0.0);

    //draw things

    if(physicsScene == NULL)
        return;

    float matrix[16];
    const static float shadowMatrix[] = { 1,0,0,0, 0,0.001f,0,0, 0,0,1,0, 0,0,0,1 };
    unsigned int nbActors = physicsScene->getNbActors();
    NxActor **actors = physicsScene->getActors();
    while(nbActors--)
    {
        NxActor *actor = *actors++;

        unsigned int nbShapes = actor->getNbShapes();
        NxShape *const *shapes = actor->getShapes();
        while(nbShapes--)
        {
            NxShape *shape = shapes[nbShapes];
            switch(shape->getType())
            {
                case NX_SHAPE_PLANE:
                {
                    NxPlaneShape *plane = shape->isPlane();
                    plane->getGlobalPose().getColumnMajor44(matrix);
                    glPushMatrix();
                    glMultMatrixf(matrix);
                    glColor3f(0.8f, 0.8f, 0.4f);
                    Renderer::DrawPlane(10.0f, 5, 5);
                    glPopMatrix();
                    break;
                }
                case NX_SHAPE_BOX:
                {
                    NxBoxShape *box = shape->isBox();
                    NxVec3 boxDimension = box->getDimensions();
                    box->getGlobalPose().getColumnMajor44(matrix);

                    glPushMatrix();
                    glMultMatrixf(matrix);
                    glColor3f(0.68f, 1.0f, 0.39f);
                    Renderer::DrawBox(boxDimension);
                    //Border
                    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                    glLineWidth(2.5f);
                    glColor3f(0.0f, 0.0f, 0.0f);
                    Renderer::DrawBox(boxDimension);
                    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                    //
                    glPopMatrix();

                    //shadow
                    glDisable(GL_LIGHTING);
                    glPushMatrix();
                    glMultMatrixf(shadowMatrix);
                    glMultMatrixf(matrix);
                    glColor4f(0.0f, 0.0f, 0.0f, 0.8f);
                    Renderer::DrawBox(boxDimension);
                    glPopMatrix();
                    glEnable(GL_LIGHTING);
                    break;
                }
                case NX_SHAPE_SPHERE:
                {
                    NxSphereShape *sphere = shape->isSphere();
                    float radius = sphere->getRadius();
                    sphere->getGlobalPose().getColumnMajor44(matrix);

                    glPushMatrix();
                    glMultMatrixf(matrix);
                    glColor3f(0.62f, 1.0f, 0.0f);
                    glutSolidSphere(radius, 32, 32);
                    glPopMatrix();

                    //shadow
                    glDisable(GL_LIGHTING);
                    glPushMatrix();
                    glMultMatrixf(shadowMatrix);
                    glMultMatrixf(matrix);
                    glColor4f(0.0f, 0.0f, 0.0f, 0.8f);
                    glutSolidSphere(radius, 32, 32);
                    glPopMatrix();
                    glEnable(GL_LIGHTING);
                    break;
                }
            }
        }
    }

    glutSwapBuffers();
}
